file-storage 크래시 안전성 로드맵 (GlueSQL #2016→#2017)
NOTE
file-storage백엔드의 크래시 안전성을 개선하는 작업을 “row 파일 하나” → “statement 전체” → “스토리지 전체(마이그레이션)” 순으로 확장해가는 로드맵. PR #2016에서 시작해 Issue #2017로 이어졌고, 설계 방향을 잡을 때 다른 기여자가 연 Issue #2004에 메인테이너(panarch)가 남긴 설계 원칙을 그대로 가져와 적용했다. 각 단계에서 “왜 여기까지만 하고 다음으로 넘겼는가”의 근거를 정리한다.
📌 개념
1단계 — #2016: 파일 하나 단위의 원자성 (진행 중)
insert_data/append_data가 row 하나를 File::create + write_all로 직접 덮어쓰던 걸, 크레이트 안에 이미 있던 “임시 파일 + fsync + rename” 패턴(write_file_atomically)으로 교체. 자세한 설계는 file-storage 원자적 쓰기 설계와 모듈 분리(#2016) 참고.
- 범위: row 파일 1개를 쓰는 동안의 크래시만 방지.
delete_data는unlink자체가 원자적이라 스코프 밖. - 명시적으로 미룬 것 두 가지 (PR 본문에 known limitation으로 적어둠):
- 백업 rename 직후~최종 rename 직전 사이 크래시 시 남는
.tmp-*/.bak-*파일의 시작 시 자동 복구 — 별도 후속 PR로 트래킹. - statement(한 번의
insert_data호출) 전체의 원자성 — row가 여러 개일 때 일부만 쓰인 채 크래시하는 문제는 이 PR의 스코프 밖. → 이게 그대로 2017이 됨.
- 백업 rename 직후~최종 rename 직전 사이 크래시 시 남는
- 현재 상태(2026-09-07 기준): 오픈 상태, CodeRabbit 자동 리뷰는 액셔너블 코멘트 없음, 다만 사람 리뷰(
panarch/devgony/ever0de/zmrdltl에게 요청됨)는 아직 없고 브랜치가 main과 드리프트됨 → rebase 후 리뷰 대기가 다음 행동.
2단계 — #2017: statement 단위의 원자성 (설계 확정, 메인테이너 피드백 대기)
멀티로우 INSERT 하나가 row N개를 순차적으로 쓰다가 중간에 크래시하면, 일부 row만 존재하는 반쪽짜리 상태가 남는 문제.
최초 제안(WAL/저널 방식): statement 시작 전 저널에 pending write를 기록 → row 적용(#2016의 write_file_atomically 재사용) → 성공 시 저널 항목 삭제 → 시작 시 미완료 항목이 있으면 replay 또는 discard.
설계를 바꾼 이유: 같은 크레이트 안의 같은 성격 문제(마이그레이션 중 크래시, #2004)에 메인테이너 panarch가 이미 답을 내놓은 상태였다. 거기서 명시적으로 “per-row resume state를 유지하는 저널”보다 “완성본을 스테이징 위치에 먼저 만들고, 완전히 끝난 뒤에만 원본을 교체” 하는 4단계(Lock → Build → Cut over → Clean up) 구조를 선호한다고 밝혔다. 저널 방식을 그대로 밀어붙이면 나중에 “왜 여기는 저널, 저기는 스테이징이냐”는 일관성 지적을 받을 가능성이 컸다.
최종 설계(스테이징+무조건 replay로 축소 적용):
- Build: statement의 row 전체를
<table>/.pending-<uuid>.ron하나에 리스트로 직렬화해서 write_all + fsync 1번으로 씀. 파일 하나 쓰기라 torn 상태 자체가 없음 — 이 파일이 존재하는 순간 statement 전체가 durable. - Apply: pending 파일을 다시 읽어서 각 row를 실제 경로에
write_file_atomically(#2016 재사용)로 반영. - Clean up: 전부 반영되면 pending 파일 삭제(단일 unlink, 원자적).
- Recovery: 재시작 시 남은
.pending-*.ron을 발견하면, 내용이 이미 fsync로 보장돼 있고write_file_atomically의 덮어쓰기가 멱등이므로 묻지도 따지지도 않고 전부 재적용(discard 분기 없음 — 항상 앞으로만 수렴).
이 설계는 원래 제안의 “row당 fsync 1번 → statement당 fsync 1번” 배치 목표도 그대로 satisfy한다(Build 단계의 fsync 1번이 곧 배치임). 이슈 본문은 2026-09-07에 이 방향으로 수정 완료(Issue #2017).
이슈를 먼저 연 이유: 새 영속 아티팩트(pending 파일)와 복구 프로토콜이 들어가는 설계 판단이라, file-storage가 “단순한 사람이 읽을 수 있는 백엔드”로 남아야 하는지(강한 트랜잭션이 필요하면 redb-storage를 쓰면 된다는 2004의 논의) 메인테이너 합의가 먼저 필요.
3단계 이후 — #2017 완료 후 남는 작업들
| 순서 | 작업 | 트래킹 | 왜 지금 안 하는가 |
|---|---|---|---|
| 3-a | .tmp-*/.bak-* 잔여 파일의 시작 시 복구 | 2016/2017 본문에 “별도 후속 PR”로 명시 | #2016·#2017 모두 “row/statement 쓰기 자체를 안전하게 만드는 것”에 집중, 복구 스캔 로직은 독립적으로 검증 가능한 별개 단위 작업 |
| 3-b | csv-storage/json-storage에 동일 패턴 확장 | #2017 Scope/non-goals에 명시 | 독립된 크레이트라 각자 쓰기 경로가 다름 — file-storage에서 패턴이 검증된 뒤 이식하는 게 순서상 안전 |
| 3-c | 여러 statement에 걸친 트랜잭션/롤백 시맨틱 | #2017 Scope/non-goals에 명시(비목표) | 이건 redb-storage가 이미 강한 트랜잭션 보장으로 커버하는 영역(#2004 논의) — file-storage에 중복 구현할 이유가 약함 |
| 3-d(병행) | #2004: 마이그레이션 전체의 원자성 | jun02160이 진행 예고 (2026-08-22 코멘트) | 직접 담당하는 이슈는 아니지만, 2017과 같은 스테이징/replay 설계 언어를 쓰므로 진행 상황을 계속 주시 — 완료되면 #2017 리뷰 시 “이미 검증된 패턴의 확장”이라는 근거로 재인용 가능 |
🔗 참고
- GlueSQL PR #2016 — Write file-storage row data atomically to prevent torn writes on crash
- GlueSQL Issue #2017 — file-storage: statement-level atomicity for multi-row writes
- GlueSQL Issue #2004 — file-storage migration applies partially and leaves no trace when it is interrupted —
panarch의 스테이징+cutover 설계 원안